/*
 * Copyright (c) 2007 OpenNETCF Consulting, LLC
 * 
 * OpenNETCF MITx11 License
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial 
 * portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using WIAVIDEOLib;
using WIALib;

namespace OpenNETCF.Wia
{
    public delegate void OnDisconnect(Camera camera);
    public delegate void OnConnect(Camera camera);

    public sealed class CameraCollection : IEnumerable<Camera>
    {
        private static CameraCollection m_instance;
        private volatile bool m_disposed = false;
        private List<Camera> m_cameraList = new List<Camera>();
        private WIALib.Wia m_wia;

        public event OnDisconnect CameraRemoved;
        public event OnConnect CameraAdded;

        public static CameraCollection GetCameras()
        {
            if (m_instance == null)
            {
                m_instance = new CameraCollection();
            }

            return m_instance;
        }

        private CameraCollection()
        {
            var v = Environment.OSVersion.Version;
            if (v.Major >= 6)
            {
                throw new PlatformNotSupportedException("WIA is not supported on platforms after Windows XP.  Try using DShow or WPD instead.");
                // DEVELOPERS: See also http://blogs.msdn.com/b/wpdblog/archive/2007/02/13/migrating-from-wia-to-wpd.aspx

            }

            m_wia = new WiaClass();
            m_wia.OnDeviceConnected += new _IWiaEvents_OnDeviceConnectedEventHandler(m_wia_OnDeviceConnected);
            m_wia.OnDeviceDisconnected += new _IWiaEvents_OnDeviceDisconnectedEventHandler(m_wia_OnDeviceDisconnected);
            Refresh();
        }

        #region --- Dispose ---
        
        private void Release()
        {
            if (m_wia != null)
            {
                Marshal.FinalReleaseComObject(m_wia);
                m_wia = null;
            }
        }

        private void Dispose(bool disposing)
        {
            if (!m_disposed)
            {
                if (disposing)
                {
                    // add any managed disposition here if we ever need it
                }

                Release();

                m_disposed = true;
            }
        }

        ~CameraCollection()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        public Camera this[int index]
        {
            get 
            {
                lock (m_cameraList)
                {
                    if((index < 0) || (index >= m_cameraList.Count)) return null;

                    return m_cameraList[index];
                }
            }
        }

        public Camera this[string cameraName]
        {
            get 
            {
                lock (m_cameraList)
                {
                    foreach (Camera cam in m_cameraList)
                    {
                        if (string.Compare(cam.Name, cameraName, true) == 0)
                        {
                            return cam;
                        }
                    }
                    return null;
                }
            }
        }

        public int Count
        {
            get { return m_cameraList.Count; }
        }

        void m_wia_OnDeviceConnected(string Id)
        {
            Collection devices = m_wia.Devices as CollectionClass;

            try
            {
                for (int i = 0; i < devices.Length; i++)
                {
                    // get the COM object
                    DeviceInfo device = (DeviceInfoClass)Marshal.CreateWrapperOfType(devices[i], typeof(DeviceInfoClass));

                    if (device.Id == Id)
                    {
                        Camera camera = new Camera(device, m_cameraList.Count);
                        m_cameraList.Add(camera);

                        if (CameraAdded != null)
                        {
                            CameraAdded(camera);
                        }
                        break;
                    }
                    else
                    {
                        Marshal.ReleaseComObject(device);
                    }
                }
            }
            finally
            {
                Marshal.ReleaseComObject(devices);
            }
        }

        void m_wia_OnDeviceDisconnected(string Id)
        {
            for(int i = 0 ; i < Count ; i++)
            {
                if (this[i].ID == Id)
                {
                    this[i].Disconnect();
                    Camera cam = this[i];
                    Remove(cam);
                    if (CameraRemoved != null)
                    {
                        CameraRemoved(cam);
                    }
                }
            }
        }

        private void RemoveAt(int index)
        {
            m_cameraList.RemoveAt(index);
        }

        private void Remove(Camera camera)
        {
            for (int i = m_cameraList.Count - 1; i >= 0 ; i--)
            {
                // all cameras after the removed item must have their index reduced
                if (m_cameraList[i].Equals(camera))
                {
                    m_cameraList.RemoveAt(i);
                    return;
                }
                else
                {
                    m_cameraList[i].Index--;
                }
            }
        }

        private void Refresh()
        {
            lock (m_cameraList)
            {
                for (int i = 0; i < m_cameraList.Count; i++)
                {
                    m_cameraList[i].Disconnect();
                }

                m_cameraList.Clear();
                Collection devices = m_wia.Devices as CollectionClass;

                for (int i = 0; i < devices.Length; i++)
                {
                    // get the COM object
                    DeviceInfo device = (DeviceInfoClass)Marshal.CreateWrapperOfType(devices[i], typeof(DeviceInfoClass));

                    // add the name to the global list
                    Camera camera = new Camera(device, i);
                    m_cameraList.Add(camera);
                }

                Marshal.ReleaseComObject(devices);
            }
        }


        public IEnumerator<Camera> GetEnumerator()
        {
            return m_cameraList.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return m_cameraList.GetEnumerator();
        }

        public void Sort()
        {
            m_cameraList.Sort();
        }
    }
}
